using System;
using System.Diagnostics;
using System.Collections.Generic;
using System.ComponentModel;
using System.Linq.Expressions;
using System.Reflection;
using CSW.Framework.Linq;
using DevExpress.XtraGrid.Columns;
using CSW.Framework.Common;
using CSW.Framework.Common.Binding.Interfaces;
using CSW.Framework.UI.Binding;
using CSW.Framework.UI.DevEx.UserControls;

namespace CSW.Framework.UI.DevEx
{
    /// <summary>
    /// Typed Grid Controller.  Type safe access to <see cref="DXGridControl" />.
    /// </summary>
    /// <typeparam name="TItem">The type implementing <see cref="INotifyPropertyChanged" />.</typeparam>
    public class TypedGridControllerLinq<TItem> : TypedGridController<TItem>, ITypedGridControllerLinq<TItem>
        where TItem : class, IBusinessObjectLinq
    {
        private readonly Dictionary<GridColumn, string> m_GridColumnFieldName = new Dictionary<GridColumn, string>();

        /// <summary>
        /// Gets the parent object.
        /// </summary>
        /// <value>The parent object.</value>
        protected IBusinessObjectLinq ParentObject { get; private set; }

        /*/// <summary>
        /// Initializes a new instance of the <see cref="TypedGridControllerLinq{TItem}"/> class.
        /// </summary>
        /// <param name="gridControl">The grid control.</param>
        public TypedGridControllerLinq(BaseGridControl gridControl)
            : this(gridControl, null, true)
        {
        }

        /// <summary>
        /// Initializes a new instance of the <see cref="TypedGridControllerLinq{TItem}"/> class.
        /// </summary>
        /// <param name="gridControl">The grid control.</param>
        /// <param name="multiSelect">if set to <c>true</c>, multi select is enabled (default = true).</param>
        public TypedGridControllerLinq(BaseGridControl gridControl, bool multiSelect)
            : this(gridControl, null, multiSelect)
        {
        }*/

        /// <summary>
        /// Initializes a new instance of the <see cref="TypedGridControllerLinq{TItem}"/> class.
        /// </summary>
        /// <param name="gridControl">The grid control.</param>
        /// <param name="exportFileName">Name of the export file, minus the extension.</param>
        public TypedGridControllerLinq(DXGridControl gridControl, string exportFileName)
            : this(gridControl, exportFileName, true)
        {
        }

        /// <summary>
        /// Initializes a new instance of the <see cref="TypedGridControllerLinq{TItem}"/> class.
        /// </summary>
        /// <param name="gridControl">The grid control.</param>
        /// <param name="exportFileName">Name of the export file, minus the extension.</param>
        /// <param name="multiSelect">if set to <c>true</c>, multi select is enabled (default = true).</param>
        public TypedGridControllerLinq(DXGridControl gridControl, string exportFileName, bool multiSelect)
            : base(gridControl, exportFileName, multiSelect)
        {
        }

        /// <summary>
        /// Gets or sets the data source.
        /// </summary>
        /// <value>The data source.</value>
        public override BindingList<TItem> DataSource
        {
            get
            {
                if (Grid.DataSource is BindingList<TItem>)
                    return (BindingList<TItem>)Grid.DataSource;
                else if (Grid.DataSource is ILinkedBindingList<TItem>)
                    return ((ILinkedBindingList<TItem>)Grid.DataSource).GetLinkedBindingList();
                else if (Grid.DataSource == null)
                    return null;

                throw new NotSupportedException(string.Format("DataSource is of type '{0}'.", Grid.DataSource.GetType()));
            }
            set
            {
                Grid.DataSource = GetBindingListWrapper(value);
                if (value != null)
                {
                    BindingList<TItem> bindingList = DataSource;
                    if (bindingList != value)
                    {
                        foreach (TItem item in value)
                        {
                            bindingList.Add(item);
                        }
                    }

                    // TODO: Hook ListChanged on value?
                }
            }
        }

        /// <summary>
        /// Adds a child relationship.
        /// </summary>
        /// <typeparam name="TParent">The type of the parent.</typeparam>
        /// <param name="bindableObject">The bindable object.</param>
        /// <param name="expression">The expression yielding the child object.</param>
        public virtual void AddChildRelationship<TParent>(TParent bindableObject, Expression<Func<TParent, BindingList<TItem>>> expression)
            where TParent : class, IBusinessObjectLinq
        {
            Guard.ArgumentNotNull(bindableObject, "bindableObject");
            Guard.ArgumentNotNull(expression, "expression");

            // The plan is to have this method be a special case handler

            BindingList<TItem> bindingList = new BindingList<TItem>();
            DataSource = bindingList;
            BindingList<TItem> bindingList2 = DataSource;

            bindableObject.PopulateChildBindingList(expression, bindingList2);

            BestFitColumns();

            ParentObject = bindableObject;
        }

        private object GetBindingListWrapper(IEnumerable<TItem> bindingList)
        {
            if (bindingList == null)
                return null;

            bool found = false;
            foreach (GridColumn column in GridView.Columns)
            {
                string fieldName;
                if (!m_GridColumnFieldName.TryGetValue(column, out fieldName))
                    fieldName = column.FieldName;

                if (fieldName != null && fieldName.Contains("."))
                {
                    found = true;
                    break;
                }
            }

            if (!found)
                return bindingList;

            Type newType = GetWrapperType(null);

            MethodInfo getBindingList = newType.GetMethod("GetEmptyBindingList", BindingFlags.Static | BindingFlags.Public);
            Debug.Assert(getBindingList != null);
            object emptyBindingList = getBindingList.Invoke(null, null);

            return emptyBindingList;
        }

        private Type GetWrapperType(IEnumerable<Where<TItem>> where)
        {
            List<string> fieldList = new List<string>();
            for (int i = 0; i < GridView.Columns.Count; i++)
            {
                GridColumn column = GridView.Columns[i];

                string fieldName;
                if (!m_GridColumnFieldName.TryGetValue(column, out fieldName))
                {
                    fieldName = column.FieldName;
                    m_GridColumnFieldName.Add(column, fieldName);
                }

                fieldList.Add(fieldName);
            }

            Type wrapperType = LinqToSqlHelper<TItem>.GetWrapperType(fieldList, where);

            for (int i = 0; i < GridView.Columns.Count; i++)
            {
                GridView.Columns[i].FieldName = fieldList[i];
            }

            return wrapperType;
        }

        /// <summary>
        /// Performs a query taking into account columns added to the grid and property branching.
        /// </summary>
        /// <param name="table">The table.</param>
        public IBindingList Query(ITableQueryProvider<TItem> table)
        {
            return Query(table, new Where<TItem>[] { });
        }

        /// <summary>
        /// Performs a query taking into account columns added to the grid and property branching.
        /// </summary>
        /// <param name="table">The table.</param>
        /// <param name="where">The where clause.</param>
        public IBindingList Query(ITableQueryProvider<TItem> table, IEnumerable<Where<TItem>> where)
        {
            Guard.ArgumentNotNull(table, "table");
            Guard.ArgumentNotNull(where, "where");

            List<string> fieldList = new List<string>();
            for (int i = 0; i < GridView.Columns.Count; i++)
            {
                GridColumn column = GridView.Columns[i];

                string fieldName;
                if (!m_GridColumnFieldName.TryGetValue(column, out fieldName))
                {
                    fieldName = column.FieldName;
                    m_GridColumnFieldName.Add(column, fieldName);
                }

                fieldList.Add(fieldName);
            }

            Type newType = LinqToSqlHelper<TItem>.GetWrapperType(fieldList, where);

            for (int i = 0; i < GridView.Columns.Count; i++)
            {
                GridView.Columns[i].FieldName = fieldList[i];
            }

            MethodInfo getBindingList = newType.GetMethod("GetNewBindingList", BindingFlags.Static | BindingFlags.Public);
            Debug.Assert(getBindingList != null);
            object bindingList = getBindingList.Invoke(null, new object[] { where });

            Grid.DataSource = bindingList;

            return (IBindingList)bindingList;
        }

        /// <summary>
        /// Creates the type async.
        /// </summary>
        /// <param name="where">The where list.</param>
        public void CreateTypeAsync(List<Where<TItem>> where)
        {
            List<string> fieldList = new List<string>();
            for (int i = 0; i < GridView.Columns.Count; i++)
            {
                GridColumn column = GridView.Columns[i];

                string fieldName;
                if (!m_GridColumnFieldName.TryGetValue(column, out fieldName))
                {
                    fieldName = column.FieldName;
                    m_GridColumnFieldName.Add(column, fieldName);
                }

                if (!fieldList.Contains(fieldName))
                    fieldList.Add(fieldName);
            }

            Func<List<string>, List<Where<TItem>>, bool> createType = CreateType;
            createType.BeginInvoke(fieldList, where, CreateTypeCallback, fieldList);
        }

        private void CreateTypeCallback(IAsyncResult ar)
        {
            if (Grid.InvokeRequired)
            {
                Grid.Invoke(new AsyncCallback(CreateTypeCallback), ar);
            }
            else
            {
                List<string> fieldList = (List<string>)ar.AsyncState;

                for (int i = 0; i < GridView.Columns.Count; i++)
                {
                    GridView.Columns[i].FieldName = fieldList[i];
                }
            }
        }

        private static bool CreateType(List<string> fieldList, List<Where<TItem>> where)
        {
            LinqToSqlHelper<TItem>.GetWrapperType(fieldList, where);
            return true;
        }
    }
}
